package co.recloud.ariadne.request;

import java.io.Serializable;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Semaphore;

import co.recloud.ariadne.model.Host;
import co.recloud.ariadne.response.Response;
import co.recloud.ariadne.server.DataServer;
import co.recloud.ariadne.store.HostTable;
import co.recloud.ariadne.thread.Main;
import co.recloud.ariadne.thread.ThreadPool;

public class Request implements Serializable, Runnable {
	/**
	 * 
	 */
	private static final long serialVersionUID = -7115733931113614067L;
	public static final int WORK = 1;
	public static final int DATA = 2;
	public static final int TRANSACTION = 3;
	public static final int HOST = 4;
	private int type;
	private Long time;
	transient private Response response;
	transient private Queue<Response> responses;
	private Host target;
	private Semaphore semaphore;
	private Long id;
	private Long hops;

	public Request() {
		time = Main.getTime();
		id = Main.getTime();
	}
	
	public Long getId() {
		return id;
	}
	
	/**
	 * @param type
	 *            the type to set
	 */
	public void setType(int type) {
		this.type = type;
	}

	/**
	 * @return the type
	 */
	public int getType() {
		return type;
	}

	/**
	 * @param time
	 *            the time to set
	 */
	public void setTime(Long time) {
		this.time = time;
	}

	/**
	 * @return the time
	 */
	public Long getTime() {
		return time;
	}

	public void sendBlocking(Host target) {
		HostTable ht = HostTable.getInstance();
		if (!ht.getLocalhost().equals(target)) {
			ConnectionPool pool = ConnectionPool.getInstance();
			Connection connection = pool.getConnection(target);
			connection.write(this);
			setResponse((Response) connection.read());
			pool.releaseConnection(connection);
		} else {
			setResponse(new DataServer().serve(this));
		}
	}

	public void sendNonBlocking(Host target, Semaphore semaphore, Queue<Response> responses) {
		this.semaphore = semaphore;
		this.responses = responses;
		ThreadPool.startThread(this);
	}

	/**
	 * @param response
	 *            the response to set
	 */
	public void setResponse(Response response) {
		if(response != null) {
			Main.setTime(response.getTime());
		}
		this.response = response;
	}

	/**
	 * @return the response
	 */
	public Response getResponse() {
		return response;
	}

	public void run() {
		sendBlocking(target);
		responses.add(response);
		semaphore.release();
	}

	public Host getTarget() {
		return target;
	}

	public void setTarget(Host target) {
		this.target = target;
	}
	
	public void gossip() {
		HostTable ht = HostTable.getInstance();
		if(hops == null) { 
			hops = 0L;
		}
		if(hops < ht.getWidth() + 1) {
			hops++;
			Set<Host> neighbors = ht.getRandomNeighbors();
			for(Host neighbor : neighbors) {
				while(true) {
					if(Main.waitForIdle()) {
						break;
					}
				}
				this.setTarget(neighbor);
				this.sendBlocking(neighbor);
			}
		}
	}
}
